package org.microboot.shiro.oauth2;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.microboot.core.constant.Constant;
import org.microboot.web.utils.RequestUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

/**
 * @author 胡鹏
 */
public class OAuth2Filter extends AuthenticatingFilter {

    /**
     * createToken()被调用的流程如下：
     * 1、OAuth2Filter的onAccessDenied()
     * 2、AuthenticatingFilter的executeLogin()
     * 3、OAuth2Filter的createToken(...)
     *
     * 补充：
     * 1、OAuth2Realm通过supports()方法判断token的类型【只有OAuth2Token类型才执行】
     * 2、createToken()方法返回OAuth2Token类型的实例，因此与OAuth2Realm匹配上了
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String principal = RequestUtils.getParameter(httpRequest, Constant.SECURITY_USERNAME);
        String credentials = RequestUtils.getParameter(httpRequest, Constant.SECURITY_TOKEN);
        return new OAuth2Token(principal, credentials);
    }

    /**
     * onAccessDenied()方法
     * 1、OncePerRequestFilter的doFilter(...)
     * 2、AdviceFilter的doFilterInternal(request, response, filterChain)
     * 3、PathMatchingFilter的preHandle(request, response)
     * 4、PathMatchingFilter的isFilterChainContinued(...)
     * 5、AccessControlFilter的onPreHandle(...)
     * 6、OAuth2Filter的onAccessDenied(...)
     *
     * 分析：
     * 1、ShiroConfig中创建了ShiroFilterFactoryBean，并且绑定了Url【"/**"】与OAuth2Filter的关系
     * 2、ShiroFilterFactoryBean是一个FactoryBean，并实现了getObject()方法
     * 2-1、在Spring源码的getBean()流程中，判断是一个FactoryBean，则会直接调用它的getObject()方法来创建Bean实例
     * 3、ShiroFilterFactoryBean实现了getObject()方法，内部执行createInstance()返回了一个SpringShiroFilter的实例
     *
     * 关于createInstance()的核心代码
     * 1、FilterChainManager manager = createFilterChainManager()
     * 2、PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver()
     * 3、chainResolver.setFilterChainManager(manager)
     * 4、return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver, getShiroFilterConfiguration())
     *
     * 关于createFilterChainManager()的核心代码
     * 1、DefaultFilterChainManager manager = new DefaultFilterChainManager()
     * 2、manager.createChain(...) 或 manager.createDefaultChain(...)
     * 2-1、manager.addToChain(...)
     * 2-1-1、NamedFilterList chain = manager.ensureChain(chainName)：将Url绑定到NamedFilterList对象中，并将NamedFilterList对象被保存在manager对象中
     * 2-1-2、chain.add(filter)：将Filter绑定到NamedFilterList对象中
     *
     * 小结：
     * 1、SpringShiroFilter中保存了PathMatchingFilterChainResolver的实例对象
     * 2、PathMatchingFilterChainResolver中保存了FilterChainManager的实例对象
     * 3、FilterChainManager中保存了NamedFilterList的实例对象
     * 4、NamedFilterList中绑定了Url和Filter集合
     * 5、SpringShiroFilter的父类OncePerRequestFilter实现了Filter的doFilter(...)方法
     *
     * 关于Filter接口
     * 1、老项目通过web.xml指定Filter
     * 2、SpringBoot项目在启动容器时通过BeanFactory容器拿到Filter
     *
     *
     * 前置知识：
     * 1、在SpringBoot启动时，会根据条件选择创建不同的ApplicationContext实例
     * 2、ServletWebServerApplicationContext重写了ApplicationContext的onRefresh()
     *
     * SpringBoot中关于Filter的核心代码【以ServletWebServerApplicationContext为例】
     * 1、Spring：refresh() -> onRefresh() -> createWebServer() -> getSelfInitializer() -> selfInitialize()
     * lambda表达式说明：
     * 1-1、创建ServletContextInitializer匿名对象
     * 1-2、实现ServletContextInitializer的onStartup()
     * 1-3、调用ServletWebServerApplicationContext的selfInitialize()
     *
     * 2、selfInitialize() -> getServletContextInitializerBeans() -> new ServletContextInitializerBeans(getBeanFactory())
     * 2-1、ServletContextInitializerBeans同时也是集合类Collection的子类，重写了iterator()方法【return this.sortedList.iterator()】
     * 2-2、foreach循环会调用集合的iterator()去轮询集合中的元素
     * 2-3、this.initializerTypes初始化默认添加一个ServletContextInitializer.class
     * 2-4、addServletContextInitializerBeans() -> getOrderedBeansOfType()在BeanFactory中查找ServletContextInitializer的实例
     * 【FilterRegistrationBean，DelegatingFilterProxyRegistrationBean都是ServletContextInitializer间接子类】
     * 2-5、addServletContextInitializerBeans() -> addServletContextInitializerBean()往this.initializers中添加实例
     * 2-6、this.initializers排序后赋值给this.sortedList
     *
     * 3、selfInitialize() -> RegistrationBean的onStartup() -> DynamicRegistrationBean的register() -> AbstractFilterRegistrationBean的addRegistration()
     * 3-1、RegistrationBean implements ServletContextInitializer
     * 3-2、DynamicRegistrationBean extends RegistrationBean
     * 3-3、AbstractFilterRegistrationBean extends DynamicRegistrationBean
     * 3-4、FilterRegistrationBean extends AbstractFilterRegistrationBean【重点】
     *
     * 4、Filter filter = getFilter()：获取Filter【FilterRegistrationBean】
     *
     * 5、servletContext.addFilter(getOrDeduceName(filter), filter)：进入Tomcat容器的逻辑添加Filter
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = getSubject(request, response);
        if (!subject.isAuthenticated()) {
            return executeLogin(request, response);
        }
        return true;
    }

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ex, ServletRequest req, ServletResponse resp) {
        throw ex;
    }
}
